Skip to content

Cookie expiration#5042

Closed
bmmcginty wants to merge 1 commit intocrystal-lang:masterfrom
bmmcginty:cookie_expiration
Closed

Cookie expiration#5042
bmmcginty wants to merge 1 commit intocrystal-lang:masterfrom
bmmcginty:cookie_expiration

Conversation

@bmmcginty
Copy link
Copy Markdown
Contributor

As mentioned in #5033, this PR adds consistent handling for the expires parameter for cookies. It changes #expires to #expiration_time and allows #expires to act as a property for the expires parameter. It also adds #max_age to allow access to the max-age parameter. Finally, documentation has been added for #expiration_time.

Comment thread spec/std/http/cookie_spec.cr Outdated
delta.should be > 9.seconds
delta.should be < 11.seconds
delta = cookie.max_age
delta.should eq 10.seconds
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could skip delta intermediary variable altogether since there's one call only.

Comment thread spec/std/http/cookie_spec.cr Outdated
delta.should be > 0.seconds
delta.should be < 1.seconds
delta = cookie.max_age
delta.should eq 0.seconds
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment thread src/http/cookie.cr Outdated
end

# Returns the `Time` at which this cookie will expire, or `nil` if it will not expire.
# Uses *max-age* and *expires* values to ccalculate the time.
Copy link
Copy Markdown
Contributor

@Sija Sija Sep 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: ccalculate

Comment thread spec/std/http/cookie_spec.cr Outdated

describe "expiration_time" do
it "by max-age=0" do
c = parse_set_cookie("bla=1; max-age=0")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c -> cookie plz

Comment thread spec/std/http/cookie_spec.cr Outdated
end

it "by old date" do
c = parse_set_cookie("bla=1; expires=Thu, 01 Jan 1970 00:00:00 -0000")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment thread src/http/cookie.cr Outdated
# Returns the `Time` at which this cookie will expire, or `nil` if it will not expire.
# Uses *max-age* and *expires* values to calculate the time.
def expiration_time
ma = @max_age
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ma -> max_age

Comment thread src/http/cookie.cr Outdated
# Uses *max-age* and *expires* values to calculate the time.
def expiration_time
ma = @max_age
ex = @expires
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment thread src/http/cookie.cr Outdated
def expired?
if e = expires
e < Time.now
ma = @max_age
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment thread src/http/cookie.cr Outdated
if e = expires
e < Time.now
ma = @max_age
ex = @expires
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment thread src/http/cookie.cr
parse_time(match["expires"]?)
end
expires = parse_time(match["expires"]?)
max_age = match["max_age"]? ? match["max_age"].to_i.seconds : nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max_age = match["max_age"]?.try(&.to_i.seconds)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggestion seems better than the current code.

@RX14 RX14 added breaking-change May have breaking effect in some edge cases. Mostly negligible, but still noteworthy. kind:feature topic:stdlib labels Oct 6, 2017
Comment thread spec/std/http/cookie_spec.cr Outdated
describe "expiration_time" do
it "by max-age=0" do
cookie = parse_set_cookie("bla=1; max-age=0")
(cookie.expiration_time.not_nil! <= (Time.now + 1.seconds)).should eq true
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eq true should be written as be_true in all specs.
And the tested value is probably better understandable as Time.now - cookie.expiration_time <= 1.seconds

Comment thread spec/std/http/cookie_spec.cr Outdated
end

it "not expired" do
(parse_set_cookie("bla=1; max-age=1").expiration_time.not_nil! > Time.now).should eq true
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a cookie variable like in the other examples. This makes it better readable.
The same goes for the following specs.

Comment thread spec/std/http/cookie_spec.cr Outdated
cookie.expiration_time.should eq Time.new(1970, 1, 1, 0, 0, 0)
end

it "not expired" do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to add more detail to the spec name, e.g. with max-age

Comment thread spec/std/http/cookie_spec.cr Outdated
(parse_set_cookie("bla=1; max-age=1").expiration_time.not_nil! > Time.now).should eq true
end

it "not expired" do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to add more detail to the spec name, e.g. with expires

Comment thread spec/std/http/cookie_spec.cr Outdated
(parse_set_cookie("bla=1; expires=Thu, 01 Jan 2020 00:00:00 -0000").expiration_time.not_nil! >= Time.new(2020, 1, 1, 0, 0, 0)).should eq true
end

it "not expired" do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to add more detail to the spec name, e.g. without expiration

Comment thread spec/std/http/cookie_spec.cr Outdated
end

it "not expired" do
parse_set_cookie("bla=1").expiration_time.should eq nil
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eq nil => be_nil

Comment thread src/http/cookie.cr Outdated
property secure : Bool
property http_only : Bool
property extension : String?
property creation_time : Time
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it makes sense to have creation_time writable, and I'm not sure if it should even be readable externally.

To have a customizeable time reference, I would rather add an optional argument to expiration_time and expired?. It could be called current_time or time_reference. That would be a better API, because you don't need to change the Cookie's internal data to check its expiration at a certain point of time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I set this up as you described, though I'm happy to rewrite. I added tests for using reference_time in specs as well, though the spec naming may be too verbose.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

However on a second thought, it would probably make sense to have creation_time readable just to be transparent about it. And I'd also suggest to add creation_time as an optional named parameter to initialize. This allows for time-independent testing as well as (re-)creating a Cookie instance which was retrieved at a different point in time, maybe for serialization.
I think it makes generally sense to allow all references to Time.new to be replaced with an optional parameter. So maybe even expired? should get a second, optional named parameter...

Comment thread src/http/cookie.cr Outdated
# Returns the `Time` at which this cookie will expire, or `nil` if it will not expire.
# Uses *max-age* and *expires* values to calculate the time.
def expiration_time
max_age = @max_age
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest to move the variable assignment into the condition.
The elsif branch is unnecessary because @expires is either nil or Time and this is what this method returns.

if max_age = @max_age
  creation_time + max_age
else
  @expires
end

Comment thread src/http/cookie.cr Outdated
def expired?
if e = expires
e < Time.now
max_age = @max_age
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method has duplicated code from expiration_time and should use that method instead. This way it becomes very simple:

@max_age == 0.seconds || expiration_time.try &.<(Time.now) || false

Comment thread src/http/cookie.cr Outdated
elsif expires
expires
# By default, this function uses the creation time of this cookie as the offset for max-age, if max-age is set.
# To use a different offset, provide a `Time` object to *time_Reference*.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small typo: time_reference

Comment thread spec/std/http/cookie_spec.cr Outdated
describe "expiration_time" do
it "sets expiration_time to be current when max-age=0" do
cookie = parse_set_cookie("bla=1; max-age=0")
(Time.now - cookie.expiration_time.not_nil! <= 1.seconds).should be_true
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could even be written (Time.now - cookie.expiration_time.not_nil!).should be.<= 1.seconds

I just learned about this ;)

Comment thread spec/std/http/cookie_spec.cr Outdated
describe "expiration_time" do
it "sets expiration_time to be current when max-age=0" do
cookie = parse_set_cookie("bla=1; max-age=0")
(Time.now - cookie.expiration_time.not_nil!).should be.<= 1.seconds
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really sorry that I my suggestion was a bit inprecise. 😢 I don't know why I assumed the period to be necessary...

be <= 1.seconds works as well and reads much better.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix. No problem; I could have looked through more docs myself.

@straight-shoota
Copy link
Copy Markdown
Member

Great work! Unfortunately, I was misleading you about the proper syntax for comparison expectations. Sorry about that :/

Comment thread src/http/cookie.cr
header << "; domain=#{domain}" if domain
header << "; path=#{path}" if path
header << "; expires=#{HTTP.rfc1123_date(expires)}" if expires
header << "; max-age=#{max_age.total_seconds}" if max_age
Copy link
Copy Markdown
Contributor

@jkthorne jkthorne Jun 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think + is preferred in string concatenation.
before:

header << "; max-age=#{max_age.total_seconds}" if max_age

after:

header << "; max-age=" + max_age.total_seconds if max_age

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the rest of the headers are in the other style so this is not a change is not necessary with this PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, + is discouraged. Maybe << would be slightly faster, but interpolation is totally fine.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case this is good.

@jkthorne
Copy link
Copy Markdown
Contributor

@bmmcginty what is the status on this PR? I am just curious it looks pretty good to me.

@bmmcginty bmmcginty force-pushed the cookie_expiration branch 2 times, most recently from 74f9181 to e2e9b28 Compare June 15, 2018 02:42
Comment thread src/http/cookie.cr Outdated
end

def expired?(time_reference = @creation_time)
@max_age == 0.seconds || expiration_time(time_reference).try &.<(Time.now) || false
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why || false?

I'd prefer

time = expiration_time(time_reference)
@max_age == 0.seconds || (time && time < Time.now)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@max_age == 0.seconds || (time = expiration_time(time_reference) && time < Time.now)

Avoids calculation of expiration_time if the first condition is already true.

Comment thread src/http/cookie.cr Outdated
header << "; expires=#{HTTP.rfc1123_date(expires)}" if expires
header << "; max-age=#{max_age.total_seconds}" if max_age
header << "; expires=#{HTTP.format_time(expires)}" if expires
xheader << "; max-age=#{max_age.total_seconds}" if max_age
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xheader?

@bmmcginty
Copy link
Copy Markdown
Contributor Author

bmmcginty commented Jun 15, 2018 via email

Copy link
Copy Markdown
Member

@RX14 RX14 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And spec failed

Comment thread src/http/cookie.cr
end
end

# returns the expiration status of this cookie as a `Bool` given a creation time
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns true if this cookie is expired.
Uses max-age and expires values to calculate the time.
By default, this function uses the creation time of this cookie as the offset for max-age, if max-age is set.
To use a different offset, provide a Time object to time_reference.

@bmmcginty bmmcginty force-pushed the cookie_expiration branch from fc8c27e to 354bc8c Compare June 19, 2018 03:19
@bmmcginty
Copy link
Copy Markdown
Contributor Author

I'd like to say this has been rebased to match the master branch. At this point, though, I haven't a clue.

@straight-shoota
Copy link
Copy Markdown
Member

It seems like you lost the specs and some code in cookie.cr at some point during rebasing.

@Sija
Copy link
Copy Markdown
Contributor

Sija commented Jun 19, 2018

@bmmcginty ac6a53e could be a recovery starting point.

@bmmcginty bmmcginty force-pushed the cookie_expiration branch from 354bc8c to aae5f30 Compare June 19, 2018 17:34
@straight-shoota straight-shoota added the pr:needs-work A PR requires modifications by the author. label Feb 15, 2019
@RX14 RX14 closed this Jul 16, 2019
@straight-shoota
Copy link
Copy Markdown
Member

Closed in favour of #7925

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change May have breaking effect in some edge cases. Mostly negligible, but still noteworthy. kind:feature pr:needs-work A PR requires modifications by the author. topic:stdlib

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants